//
//  XMPP.mm
//  cocoa-jabber-messenger
//
//  Created by Sangeun Kim on 4/16/11.
//  Copyright 2011 NHN Corporation. All rights reserved.
//

#import "XMPP.h"
#import "SynthesizeSingleton.h"
#import "XMPPConnectionDelegate.h"
#import "XMPPRosterDelegate.h"
#import "ContactItem.h"
#import "XMPPSession.h"


#include <gloox/client.h>
#include <gloox/rostermanager.h>
#include <gloox/messagehandler.h>
#include <gloox/presencehandler.h>
#include <gloox/vcardhandler.h>
#include <gloox/connectionlistener.h>
#include <gloox/rosterlistener.h>
#include <gloox/error.h>
#include <gloox/vcardmanager.h>
#include <gloox/mutex.h>
#include <gloox/pubsubmanager.h>
#include <gloox/messagesessionhandler.h>
#include <gloox/messagesession.h>

@interface HandlerWrapper : NSObject {
@private
    gloox::Presence* presence;
    gloox::Message* message;
    gloox::MessageSession* session;
}
@property (assign) gloox::Presence* presence;
@property (assign) gloox::Message* message;
@property (assign) gloox::MessageSession* session;
@end

@implementation HandlerWrapper
@synthesize presence;
@synthesize message;
@synthesize session;
@end


@interface XMPP (glooxHandler)
- (void) handleMessage:( HandlerWrapper* ) wrapper;
- (void) updateContact:( ContactItem* ) item;
- (void) updateContacts:( NSMutableArray* ) contacts;
- (void) onConnect: (id) sender;
- (void) onDisconnect: (NSString*) errorString;
@end


class CXmpp:public gloox::PresenceHandler, gloox::ConnectionListener, gloox::VCardHandler, gloox::RosterListener, gloox::MessageSessionHandler //gloox::MessageHandler, 
{
public:
    static CXmpp& instance();
    void setDelegate(XMPP* pDelegate)
    {
        m_delegateMutex.lock();
        m_pDelegage = pDelegate;
        m_delegateMutex.unlock();
    }
    bool setLoginInfo(NSString* loginId, NSString* password);
    void connect();
    void disconnect();
    void requestVcard(NSString* jid);
//    gloox::Client* clientPtr() {return m_pClient;};
    void startChat(gloox::JID& jid);
    void closeSession(gloox::MessageSession* pSession);
    
protected:
//    virtual void handleMessage( const gloox::Message& stanza,
//                               gloox::MessageSession* session = 0 );
    virtual void 	handlePresence (const gloox::Presence &presence);
    virtual void 	onConnect ();
    virtual void 	onDisconnect (gloox::ConnectionError e);
    virtual void 	onSessionCreateError (const gloox::Error *error);
    virtual bool 	onTLSConnect (const gloox::CertInfo &info);
    //    virtual void 	onResourceBind (const std::string &resource)
    //    virtual void 	onResourceBindError (const Error *error)
//    virtual void 	onStreamEvent (StreamEvent event)
    virtual void 	handleVCard (const gloox::JID &jid, const gloox::VCard *vcard);
    virtual void 	handleVCardResult (gloox::VCardHandler::VCardContext context, const gloox::JID &jid, gloox::StanzaError se=gloox::StanzaErrorUndefined);

    
    virtual void 	handleItemAdded (const gloox::JID &jid){};
    virtual void 	handleItemSubscribed (const gloox::JID &jid){};
    virtual void 	handleItemRemoved (const gloox::JID &jid){};
    virtual void 	handleItemUpdated (const gloox::JID &jid){};
    virtual void 	handleItemUnsubscribed (const gloox::JID &jid){};
    virtual void 	handleRoster (const gloox::Roster &roster);
    virtual void 	handleRosterPresence (const gloox::RosterItem &item, const std::string &resource, gloox::Presence::PresenceType presence, const std::string &msg){};
    virtual void 	handleSelfPresence (const gloox::RosterItem &item, const std::string &resource, gloox::Presence::PresenceType presence, const std::string &msg){};
    virtual bool 	handleSubscriptionRequest (const gloox::JID &jid, const std::string &msg){return false;};
    virtual bool 	handleUnsubscriptionRequest (const gloox::JID &jid, const std::string &msg){return false;};
    virtual void 	handleNonrosterPresence (const gloox::Presence &presence);
    virtual void 	handleRosterError (const gloox::IQ &iq);
    virtual void 	handleMessageSession (gloox::MessageSession *session);

private:
    CXmpp();
    virtual ~CXmpp();
    gloox::Client* m_pClient;
    gloox::PubSub::Manager* m_pPubSubManager;
    gloox::RosterManager* m_pRosterManager;
    gloox::VCardManager* m_pVcardManager;
    XMPP* m_pDelegage;
    gloox::util::Mutex  m_delegateMutex;
    
    
};

CXmpp::CXmpp()
:m_pClient(0),
m_pDelegage(0),
m_pRosterManager(0),
m_pVcardManager(0),
m_pPubSubManager(0)
{
    
}

CXmpp::~CXmpp()
{
    disconnect();
    
    delete m_pClient;
    m_pClient = 0;
    
    NSLog(@"class CXmpp destoried");
}

CXmpp& CXmpp::instance()
{
    static CXmpp xmpp;
    return xmpp;
}

void CXmpp::disconnect()
{
    if (m_pClient) {
        if ( m_pClient->state() != gloox::StateDisconnected )
        {
            NSLog(@"disconnecting...");
            m_pClient->disconnect();
            NSLog(@"disconnected");
            delete m_pVcardManager;
            m_pVcardManager = 0;
            if (m_pRosterManager) {
                m_pRosterManager->removeRosterListener();
            }
//            delete m_pRosterManager;
            delete m_pClient;
            m_pClient=0;
            m_pRosterManager = 0;
        }
    }
}

bool CXmpp::setLoginInfo(NSString* loginId, NSString* password)
{
    delete m_pClient;
    delete m_pVcardManager;
    
    gloox::JID jid([[loginId stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding] cStringUsingEncoding:NSASCIIStringEncoding]);
    m_pClient = new gloox::Client(jid, [[password stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding] cStringUsingEncoding:NSASCIIStringEncoding]);
//    m_pClient->registerMessageHandler( this );
    m_pClient->registerPresenceHandler( this );
    m_pClient->registerConnectionListener( this );
    m_pRosterManager = m_pClient->rosterManager();
    m_pRosterManager->registerRosterListener(this, false);
    m_pRosterManager->fill();
    m_pVcardManager = new gloox::VCardManager(m_pClient);
    m_pPubSubManager = new gloox::PubSub::Manager(m_pClient);
    m_pClient->registerMessageSessionHandler( this );

    return true;
    
}

void CXmpp::connect()
{
    m_pClient->connect( true );    
}

void CXmpp::requestVcard(NSString* jid)
{
    if (!m_pVcardManager) {
        return;
    }
    
    gloox::JID id([jid UTF8String]);
    
    m_pVcardManager->fetchVCard(id, this);
    
}

void CXmpp::startChat(gloox::JID& jid)
{
    if (!m_pClient) {
        return;
    }
    
    gloox::MessageSession* pSession = new gloox::MessageSession( m_pClient, jid );
    XMPPSession* session = [[XMPPSession alloc]init];
    [session setSession:pSession];
    [session setXmpp:m_pDelegage];
    [[m_pDelegage sessionManager]addSession:session];
    
    /////////////////////////////////
    // Do NOT relese session here!
    /////////////////////////////////
    
}

void CXmpp::closeSession(gloox::MessageSession* pSession)
{
    m_pClient->disposeMessageSession(pSession);
}

#pragma mark -


#pragma mark *** ConnectionListener ***
void 	CXmpp::onConnect ()
{
    m_delegateMutex.lock();
    if( !m_pDelegage )
    {
        m_delegateMutex.unlock();
        return;
    }
    [m_pDelegage performSelectorOnMainThread:@selector(onConnect:) withObject:nil waitUntilDone:NO];
    m_delegateMutex.unlock();
    

    
}

void 	CXmpp::onDisconnect (gloox::ConnectionError e)
{
    delete m_pVcardManager;
    m_pVcardManager = 0;

    delete m_pPubSubManager;
    m_pPubSubManager = 0;
    
    m_delegateMutex.lock();
    if( !m_pDelegage )
    {
        m_delegateMutex.unlock();
        return;
    }
    
    NSString* errorString = [[NSString alloc] initWithFormat:@"%d", e];
    [m_pDelegage performSelectorOnMainThread:@selector(onDisconnect:) withObject:errorString waitUntilDone:NO];
    m_delegateMutex.unlock();
}

void 	CXmpp::onSessionCreateError (const gloox::Error *error)
{
    m_delegateMutex.lock();
    if( !m_pDelegage )
    {
        m_delegateMutex.unlock();
        return;
    }
    
    std::string errorString;
    if (error) {
        errorString = error->text();
    }
    else
    {
        errorString = "";
    }
    
    NSString* errorMessage = [[NSString init ]initWithCString:errorString.c_str() encoding:NSASCIIStringEncoding];
    [m_pDelegage performSelectorOnMainThread:@selector(onDisconnect:) withObject:errorMessage waitUntilDone:NO];
    m_delegateMutex.unlock();
    
}

bool 	CXmpp::onTLSConnect (const gloox::CertInfo &info)
{
    return true;
}
#pragma mark -

#pragma mark *** VCard Handlers ***

void 	CXmpp::handleVCard (const gloox::JID &jid, const gloox::VCard *vcard)
{
    if (!jid.bare().length()) {
        return;
    }
    m_delegateMutex.lock();
    if (!m_pDelegage) {
        m_delegateMutex.unlock();
        return;
    }
    
    ContactItem* item = [[ContactItem alloc]init];
    [item setVcard:YES];
    [item setJid:[NSString stringWithUTF8String:jid.bare().c_str()]];
    [item setFullJid:[NSString stringWithUTF8String:jid.full().c_str()]];
    if (vcard->photo().extval == "") {
        NSData* imageData = [NSData dataWithBytes:vcard->photo().binval.c_str() length:vcard->photo().binval.size()];
        [item setPhoto:imageData];
        [imageData release];
    }
    [item setName:[NSString stringWithUTF8String:vcard->formattedname().c_str()]];
    
    [m_pDelegage performSelectorOnMainThread:@selector(updateContact:) withObject:item waitUntilDone:NO];
    
    m_delegateMutex.unlock();
}
void 	CXmpp::handleVCardResult (gloox::VCardHandler::VCardContext context, const gloox::JID &jid, gloox::StanzaError se)
{
    
}

#pragma mark -

#pragma mark *** RosterListener ***
void 	CXmpp::handleRoster (const gloox::Roster &roster)
{
    m_delegateMutex.lock();
    if( !m_pDelegage )
    {
        m_delegateMutex.unlock();
        return;
    }
    
    gloox::Roster* pRoster = new gloox::Roster(roster);
    gloox::Roster::iterator it;
    NSMutableArray* contacts = [[NSMutableArray alloc ]initWithCapacity:pRoster->size()];
    for (it = pRoster->begin(); it != pRoster->end(); it++) {
        NSString* strKey = [NSString stringWithUTF8String:(*it).first.c_str()];
        gloox::RosterItem* pItem = (*it).second;
        NSString* strJid = [NSString stringWithUTF8String:pItem->jid().c_str()];
        
        NSMutableArray* groups = [[NSMutableArray alloc]init ];
        gloox::StringList list(pItem->groups());
        gloox::StringList::iterator it;
        for (it = list.begin(); it != list.end(); it++)
        {
            [groups addObject:[NSString stringWithUTF8String:(*it).c_str()]];            
        }
        NSString* strName = [NSString stringWithUTF8String:pItem->name().c_str()];
        BOOL online = pItem->online();
        
        ContactItem* item = [[ContactItem alloc]init];
        [item setJid:strJid];
        [item setKey:strKey];
        [item setName:strName];
        [item setOnline:online];
        [item setGroups:[NSArray arrayWithArray:groups]];
        [contacts addObject:item];
        [item release];
    }
    [m_pDelegage performSelectorOnMainThread:@selector(updateContacts:) withObject:contacts waitUntilDone:NO];

    m_delegateMutex.unlock();
}

void 	CXmpp::handleNonrosterPresence (const gloox::Presence &presence)
{

}
void 	CXmpp::handleRosterError (const gloox::IQ &iq)
{
    
}
#pragma mark -


#pragma mark *** Message Handlers ***
void 	CXmpp::handleMessageSession (gloox::MessageSession *session)
{
    XMPPSession* s = [[XMPPSession alloc]init];
    [s setSession:session];
    [s setIncomingSession:YES];
    [s setXmpp:m_pDelegage];
    [[m_pDelegage sessionManager] performSelectorOnMainThread:@selector(addSession:) withObject:s waitUntilDone:YES];
}

/*
void CXmpp::handleMessage( const gloox::Message& stanza,
                          gloox::MessageSession* session )
{
    m_delegateMutex.lock();
    if( !m_pDelegage )
    {
        m_delegateMutex.unlock();
        return;
    }

    
    HandlerWrapper* wrapper = [[HandlerWrapper alloc]init];
        
    [wrapper setMessage:(gloox::Message*)&stanza];
    [wrapper setSession:session];
    [m_pDelegage performSelectorOnMainThread:@selector(handleMessage:) withObject:wrapper waitUntilDone:NO];
    m_delegateMutex.unlock();
}
*/

#pragma mark -

#pragma mark *** Presence Handlers ***

void 	CXmpp::handlePresence (const gloox::Presence &presence)
{
    m_delegateMutex.lock();
    if( !m_pDelegage )
    {
        m_delegateMutex.unlock();
        return;
    }
    
    ContactItem* item = [[ContactItem alloc]init];
    [item setJid:[NSString stringWithUTF8String:presence.from().bare().c_str()]];
    [item setFullJid:[NSString stringWithUTF8String:presence.from().full().c_str()]];
    [item setStatus:[NSString stringWithUTF8String:presence.status().c_str()]];
    [item setPresence:presence.subtype()];
//    [item setOnline:YES];
//     [item setVcard:YES];
    
    [m_pDelegage performSelectorOnMainThread:@selector(updateContact:) withObject:item waitUntilDone:NO];
    m_delegateMutex.unlock();
    
}


#pragma mark -

#pragma mark *** XMPPThread ***
@interface XMPPThread : NSThread {
@private
}
@end

@implementation XMPPThread

- (id)init
{
    self = [super init];
    if (self) {
        // Initialization code here.
        NSLog(@"XMPPThread initialized");
    }
    return self;
}

- (void)dealloc
{
    [super dealloc];
    NSLog(@"XMPPThread destroyed");
}

- (void)main
{
    NSLog(@"XMPP thread started");
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc]init];
    CXmpp::instance().connect();
    NSLog(@"XMPP thread ended");
    [pool release];
}

@end
#pragma mark -

#pragma mark *** XMPP Implementation ***
@implementation XMPP
@synthesize rosterDelegate;

//SYNTHESIZE_SINGLETON_FOR_CLASS(XMPP)

- (id)init
{
    self = [super init];
    if (self) {
        // Initialization code here.
        connectionDelegates = [[NSMutableArray alloc]init];
        NSLog(@"XMPP initialized");
    }
    return self;
}

- (void)dealloc
{
//    [self disconnect];
    CXmpp::instance().setDelegate(nil);
    [connectionDelegates release];
    [thread release];
    [super dealloc];
    NSLog(@"XMPP destroyed");
}

- (XMPPSessionManager*) sessionManager
{
    return sessionManager;
}


- (BOOL) isFinished
{
    if (!thread) {
        return YES;
    }
    return [thread isFinished];
}

- (void) registerConnectionDelegate:(id < XMPPConnectionDelegate >) connectionDelegate
{
    [connectionDelegates addObject:connectionDelegate];
}

- (void) deregisterConnectionDelegate:(id < XMPPConnectionDelegate >) connectionDelegate
{
    [connectionDelegates removeObject:connectionDelegate];
}

- (BOOL)loginWithId:(NSString*)loginId withPassword:(NSString*)password
{
    CXmpp::instance().setDelegate(self);
    CXmpp::instance().setLoginInfo(loginId, password);
    if (thread) {
        if ([thread isExecuting])
        {
            [NSException raise:@"Cannot do login process" format:@"Thread is already running"];
            return NO;
        }
    }
    [thread release];
    thread = [[XMPPThread alloc]init];
    [thread start];
    return YES;
}

- (void) disconnect
{
    CXmpp::instance().disconnect();
    while ([thread isExecuting]) {
        sleep(10);
    }
    [thread release];
    thread = nil;
    
    CXmpp::instance().setDelegate(nil);
    
}


#pragma mark -

- (void) startChat:(NSString*)jid
{
    if ([sessionManager activateSession:jid]) {
        return;
    }
    gloox::JID glooxJid([jid UTF8String]);
    CXmpp::instance().startChat(glooxJid);
    
}

#pragma mark *** XMPP callback handlers ***
- (void) onConnect: (id) sender
{
    NSEnumerator* e = [connectionDelegates objectEnumerator];
    id < XMPPConnectionDelegate > connectionDelegate;
    while ((connectionDelegate = [e nextObject])) {
        [connectionDelegate onConnect];
    }
}

- (void) onDisconnect: (NSString*) errorString
{
    gloox::ConnectionError error = (gloox::ConnectionError)[errorString intValue];
    
    NSEnumerator* e = [connectionDelegates objectEnumerator];
    id < XMPPConnectionDelegate > connectionDelegate;
    while ((connectionDelegate = [e nextObject])) {
        [connectionDelegate onDisconnectWithErrorCode:error];
    }
    [errorString release];
}

- (void) handleMessage:( HandlerWrapper* ) wrapper
{
    
}

- (void) updateContact:( ContactItem* ) item;
{
    if (!rosterDelegate) {
        [item release];
        return;
    }
    
    [rosterDelegate onRoster:item];
    if (![item vcard]) {
        CXmpp::instance().requestVcard([item fullJid]);
    }
    [item release];
}

- (void) updateContacts:( NSMutableArray* ) contacts
{
    if (!rosterDelegate) {
        [contacts release];
        return;
    }
    
    for (ContactItem* contact in contacts) {
        [rosterDelegate onRoster:contact];
        if (![contact vcard]) {
            CXmpp::instance().requestVcard([contact fullJid]);
        }
    }
    [contacts release];
    
}



#pragma mark -

- (void) close:(XMPPSession*) session
{
    CXmpp::instance().closeSession([session session]);
    [sessionManager performSelector:@selector(removeSession:) withObject:session afterDelay:0];
}

@end
